#include "config.h"
#include <fstream>
#include "nlohmann/json.hpp"
#include "log.h"
#include "string_util.h"

using json = nlohmann::json;

namespace nlohmann {
template <>
struct adl_serializer<std::wstring> {
    static void to_json(json &j, const std::wstring &str)
    {
        j = UnicodeToUtf8(str);
    }

    static void from_json(const json &j, std::wstring &str)
    {
        str = Utf8ToUnicode(j);
    }
};
template <>
struct adl_serializer<std::map<std::wstring, BlockType>> {
    static void to_json(json &j, const std::map<std::wstring, BlockType> &m)
    {
        for (const auto &pair : m) {
            j[UnicodeToUtf8(pair.first)] = pair.second;
        }
    }

    static void from_json(const json &j, std::map<std::wstring, BlockType> &m)
    {
        for (auto iter = j.begin(); iter != j.end(); ++iter) {
            m[Utf8ToUnicode(iter.key())] = iter.value();
        }
    }
};
}  // namespace nlohmann

namespace {
bool Contains(std::vector<std::wstring> &target, const std::wstring &e)
{
    for (auto &item : target) {
        if (item == e) {
            return true;
        }
    }
    return false;
}
}  // namespace

void from_json(const nlohmann::json &j, AppInfo &item)
{
    item.name = j.at("name").get<std::wstring>();
    if (j.contains("product_name_list")) {
        item.productNameList = j.at("product_name_list").get<std::vector<std::wstring>>();
    }
    if (j.contains("file_name_pattern_list")) {
        item.fileNamePatternList = j.at("file_name_pattern_list").get<std::vector<std::wstring>>();
    }
    if (j.contains("tag")) {
        item.tag = j.at("tag").get<std::wstring>();
    }
}

void to_json(nlohmann::json &j, const AppInfo &info)
{
    j["name"] = info.name;
    j["product_name_list"] = info.productNameList;
    j["file_name_pattern_list"] = info.fileNamePatternList;
    j["tag"] = info.tag;
}

void from_json(const nlohmann::json &j, BlockType &type)
{
    if (j.contains("enable")) {
        type.enable = j.at("enable").get<bool>();
    }
    if (j.contains("title")) {
        type.title = j.at("title").get<std::wstring>();
    }
    if (j.contains("content")) {
        type.content = j.at("content").get<std::wstring>();
    }
}

void to_json(nlohmann::json &j, const BlockType &type)
{
    j["enable"] = type.enable;
    j["title"] = type.title;
    j["content"] = type.content;
}

void from_json(const nlohmann::json &j, Policy &policy)
{
    if (j.contains("path_trust_list")) {
        policy.pathTrustList = j.at("path_trust_list").get<std::vector<std::wstring>>();
    }
    if (j.contains("app_trust_list")) {
        policy.appTrustList = j.at("app_trust_list").get<std::vector<AppInfo>>();
    }
    if (j.contains("entry_block_list")) {
        policy.entryBlockList = j.at("entry_block_list").get<std::vector<std::wstring>>();
    }
    if (j.contains("app_block_list")) {
        policy.appBlockList = j.at("app_block_list").get<std::vector<AppInfo>>();
    }
    if (j.contains("block_types")) {
        j.at("block_types").get_to(policy.blockTypes);
    }
}

void to_json(nlohmann::json &j, const Policy &policy)
{
    j["path_trust_list"] = policy.pathTrustList;
    j["app_trust_list"] = policy.appTrustList;
    j["app_block_list"] = policy.appBlockList;
    j["entry_block_list"] = policy.entryBlockList;
    j["block_types"] = policy.blockTypes;
}

void from_json(const nlohmann::json &j, Config &config)
{
    if (j.contains("log_level")) {
        config.logLevel = j.at("log_level").get<std::string>();
    }
    if (j.contains("log_path")) {
        config.logPath = j.at("log_path").get<std::string>();
    }
    if (j.contains("policy")) {
        config.policy = j.at("policy").get<Policy>();
    }
}

void to_json(nlohmann::json &j, const Config &config)
{
    j["log_level"] = config.logLevel;
    j["log_path"] = config.logPath;
    j["policy"] = config.policy;
}

FilterConfig::FilterConfig(const std::string &path)
{
    this->configPath = path;
    Init();
}

std::string FilterConfig::GetLogPath() const
{
    return this->config.logPath;
}

std::string FilterConfig::GetLogLevel() const
{
    return this->config.logLevel;
}

std::vector<std::wstring> FilterConfig::GetTrustedPathList()
{
    char* userprofile = getenv("USERPROFILE");
    if (userprofile != nullptr) {
        std::wstring appdata = Utf8ToUnicode(userprofile).append(L"\\AppData\\");
        config.policy.pathTrustList.push_back(appdata);
    }
    return config.policy.pathTrustList;
}

std::vector<std::wstring> FilterConfig::GetTrustedProductNameList() const
{
    std::vector<std::wstring> productNameList;
    for (auto &app : config.policy.appTrustList) {
        for (const auto &productName : app.productNameList) {
            if (!productName.empty() && !Contains(productNameList, productName)) {
                productNameList.push_back(productName);
            }
        }
    }
    return productNameList;
}

std::vector<std::wstring> FilterConfig::GetTrustedFileNamePatternList() const
{
    std::vector<std::wstring> patternList;
    for (auto &item : config.policy.appTrustList) {
        for (const auto &pattern : item.fileNamePatternList) {
            if (!pattern.empty() && !Contains(patternList, pattern)) {
                patternList.push_back(pattern);
            }
        }
    }
    return patternList;
}

std::vector<std::wstring> FilterConfig::GetUnTrustedProcessNameList() const
{
    std::vector<std::wstring> ret;
    for (const auto &item : config.policy.entryBlockList) {
        if (!item.empty() && !Contains(ret, item)) {
            ret.push_back(item);
        }
    }
    return ret;
}

std::vector<AppInfo> FilterConfig::GetBlockAppList() const
{
    return config.policy.appBlockList;
}

std::map<std::wstring, BlockType> FilterConfig::GetBlockTypes() const
{
    return config.policy.blockTypes;
}

void FilterConfig::Init()
{
    if (!std::filesystem::exists(this->configPath)) {
        LOG_ERR("config not exist.");
        return;
    }
    std::ifstream configFile(this->configPath);
    if (!configFile.is_open()) {
        LOG_ERR("open config failed.");
        return;
    }
    json data = json::parse(configFile);
    configFile.close();
    config = data.get<Config>();
}